package com.aleyn.processor.generator

import com.aleyn.processor.data.ConstructorParameter
import com.aleyn.processor.data.DependencyKind
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSTypeArgument
import com.google.gson.GsonBuilder
import java.io.OutputStream

/**
 * @author: Aleyn
 * @date: 2023/7/24 16:15
 *
 * 提供在 KSP (Kotlin Symbol Processing) 代码生成过程中所需的通用工具函数和扩展属性。
 *
 * 这个文件包含了一系列辅助方法，用于简化类型检查、泛型处理、文件操作等常见任务，
 * 从而使代码生成逻辑（主要在 `GennertorExt.kt` 中）更加清晰和健壮。
 */

/**
 * 用于 JSON 序列化和反序列化的 Gson 实例。
 * 在 LRouter 中，主要用于处理通过 `@Autowired` 传递的复杂对象参数，
 * 这些对象会被序列化成 JSON 字符串进行传递，然后在目标页面反序列化回来。
 */
internal val gson = GsonBuilder().create()

/**
 * 检查一个字符串表示的类型名称是否为 Java/Kotlin 的基本数据类型或 String。
 *
 * @receiver [String] 要检查的类型名称 (例如 "int", "String")。
 * @return 如果是基本类型或 String，则返回 `true`，否则返回 `false`。
 */
internal fun String.isPrimitiveAndString(): Boolean {
    return when (this.lowercase()) {
        "byte",
        "short",
        "int",
        "long",
        "float",
        "double",
        "boolean",
        "char",
        "string" -> true

        else -> false
    }
}

/**
 * 检查一个 KSP 类声明是否实现了 `Parcelable` 或 `Serializable` 接口。
 *
 * 这对于 `@Autowired` 的实现至关重要，因为它决定了参数是否可以直接通过 `Bundle`
 * 进行传递（`Parcelable` 效率更高），还是需要回退到基于 JSON 的序列化方案。
 *
 * @receiver [KSClassDeclaration] 要检查的 KSP 类声明。
 * @return 如果该类直接或间接实现了 `Parcelable` 或 `Serializable` 接口，则返回 `true`。
 */
internal fun KSClassDeclaration.isParcelable(): Boolean {
    return this.superTypes
        .mapNotNull { it.resolve().declaration.qualifiedName?.asString() }
        .any {
            it == "android.os.Parcelable" || it == "java.io.Serializable"
        }
}

/**
 * 递归地将 KSP 的泛型类型参数列表转换为一个完整的、可用于代码生成的字符串。
 * 例如，对于 `Map<String, List<User>>`，它会生成 `"<java.lang.String, kotlin.collections.List<com.example.User>>"`。
 *
 * @receiver [List<KSTypeArgument>] KSP 的类型参数列表。
 * @return 拼接好的泛型字符串，如果列表为空则返回空字符串。
 */
internal fun List<KSTypeArgument>.getTypeStr(): String {
    var typeArg = ""
    forEachIndexed { index, it ->
        val type = it.type?.resolve() ?: return ""
        typeArg += if (index == 0) "<" else ", "

        typeArg += type.declaration.qualifiedName!!.asString()
        val childArg = it.type!!.element?.typeArguments
        if (!childArg.isNullOrEmpty()) {
            typeArg += childArg.getTypeStr()
        }
        if (index == size - 1) typeArg += ">"
    }
    return typeArg
}

/**
 * 获取 KSP 属性声明的完整类型字符串，包括其泛型参数。
 * 例如，对于属性 `val users: List<User>`，它将返回 `"kotlin.collections.List<com.example.User>"`。
 *
 * @receiver [KSPropertyDeclaration] 要获取其完整类型的属性声明。
 * @return 包含完整泛型信息的类型字符串。
 */
internal fun KSPropertyDeclaration.getAllTypeStr(): String {
    return type.toString() + type.element?.typeArguments?.getTypeStr().orEmpty()
}

/**
 * 根据构造函数参数的元数据列表，生成用于依赖注入实例化的 Kotlin 代码字符串。
 *
 * 此函数是实现依赖注入（DI）的核心部分。它会根据参数的类型（是来自 DI 容器的依赖，
 * 还是来自外部的参数）以及是否可空，动态生成相应的 `get()`、`getOrNull()`、`getAll()`
 * 或 `it.get()` 等调用代码。
 *
 * @param constructorParameters 构造函数的参数元数据列表。
 * @return 拼接好的参数注入字符串，例如 `"(get(), getOrNull(sq(\"my_qualifier\")), it.get())"`。
 */
internal fun generateConstructor(constructorParameters: List<ConstructorParameter>): String {
    return constructorParameters.joinToString(
        prefix = "(",
        separator = ",",
        postfix = ")"
    ) { param ->
        val isNullable = param.nullable
        when (param) {
            is ConstructorParameter.Dependency -> {
                when (param.kind) {
                    DependencyKind.List -> "getAll()"
                    else -> {
                        val qualifier = param.value?.let { "sq(\"$it\")" }.orEmpty()
                        if (isNullable) "getOrNull($qualifier)" else "get($qualifier)"
                    }
                }
            }

            is ConstructorParameter.ParameterInject -> if (!isNullable) "it.get()" else "it.getOrNull()"
        }
    }
}

/**
 * `CodeGenerator` 的一个便利扩展函数，用于创建新文件，并能优雅地处理 `FileAlreadyExistsException`。
 *
 * 在 KSP 的增量编译模式下，多次处理可能会导致尝试创建已存在的文件。此函数通过捕获异常
 * 并返回现有文件的输出流，来确保代码生成过程的健壮性，允许在后续写入时覆盖旧内容。
 *
 * @receiver [CodeGenerator] KSP 代码生成器实例。
 * @param packageName 目标文件的包名。
 * @param fileName 目标文件的名称。
 * @return 一个可用于写入文件内容的 [OutputStream]。
 */
internal fun CodeGenerator.getFile(packageName: String, fileName: String): OutputStream {
    return try {
        createNewFile(
            Dependencies.ALL_FILES,
            packageName,
            fileName
        )
    } catch (ex: FileAlreadyExistsException) {
        ex.file.outputStream()
    }
}